// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "CaeAesUtils.h"

#include <openssl/err.h>

#include "../hwsecure/securec.h"

using namespace std;
bool CaeAesGcmDecrypt(std::string &encryptDataHex, const AesInfo *aesInfo, std::string &outData)
{
    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();

    auto cipherTextStr = encryptDataHex;
    if (cipherTextStr.empty()) {
        LOGE("CipherTextStr is invalid.");
        return false;
    }
    string ivStr = aesInfo->strIv;
    if (ivStr.empty()) {
        LOGE("IvStr is invalid.");
        return false;
    }
    string keyStr = aesInfo->strKey;
    if (keyStr.empty()) {
        LOGE("KeyStr is invalid.");
        return false;
    }

    uint32_t cipherTextLen = 0;
    uint8_t *outDataCipher = nullptr;
    std::vector<uint8_t> cipherTextOld;
    cipherTextOld = HexToBytes(cipherTextStr);
    cipherTextLen = cipherTextOld.size();
    if (cipherTextLen <= 16) {
        LOGE("CipherTextLen is invalid, size is %d.", cipherTextLen);
        return false;
    }
    outDataCipher = cipherTextOld.data();

    int ivLen = 0;
    uint8_t *iv = nullptr;
    std::vector<uint8_t> ivOld;
    ivOld = HexToBytes(ivStr);
    ivLen = static_cast<int>(ivOld.size());
    if (ivLen <= 0) {
        LOGE("Iv is invalid, iv size is %d.", ivLen);
        return false;
    }
    iv = ivOld.data();

    uint8_t *key = nullptr;
    std::vector<uint8_t> keyOld;
    keyOld = HexToBytes(keyStr);
    if (keyOld.empty()) {
        LOGE("Key is invalid, key size is empty.");
        return false;
    }
    key = keyOld.data();

    uint8_t *cipherPayLoad = nullptr;
    uint32_t cipherPayLoadLen = 0;
    cipherPayLoad = outDataCipher;
    cipherPayLoadLen = cipherTextLen - 16;

    uint8_t *tag = nullptr;
    int tagLen = 0;
    tag = cipherPayLoad + cipherPayLoadLen;
    tagLen = 16;

    EVP_CIPHER_CTX *ctx = nullptr;
    ctx = EVP_CIPHER_CTX_new();

    const EVP_CIPHER *cipher = nullptr;
    cipher = EVP_aes_128_gcm();
    if (nullptr == cipher) {
        LOGE("EVP_aes_128_gcm failed.");
        EVP_CIPHER_CTX_free(ctx);
        return false;
    }

    if (EVP_DecryptInit_ex(ctx, cipher, nullptr, nullptr, nullptr) != 1) {
        LOGE("EVP_DecryptInit_ex failed, %s.", ERR_reason_error_string(ERR_get_error()));
        EVP_CIPHER_CTX_free(ctx);
        return false;
    }
    if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, ivLen, nullptr) != 1) {
        LOGE("EVP_CIPHER_CTX_ctrl failed, %s.", ERR_reason_error_string(ERR_get_error()));
        EVP_CIPHER_CTX_free(ctx);
        return false;
    }
    if (EVP_DecryptInit_ex(ctx, nullptr, nullptr, key, iv) != 1) {
        LOGE("EVP_DecryptInit_ex failed, %s.", ERR_reason_error_string(ERR_get_error()));
        EVP_CIPHER_CTX_free(ctx);
        return false;
    }
    if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, tagLen, tag) != 1) {
        LOGE("EVP_CIPHER_CTX_ctrl failed, %s.", ERR_reason_error_string(ERR_get_error()));
        EVP_CIPHER_CTX_free(ctx);
        return false;
    }
    size_t tmpLen = cipherPayLoadLen + EVP_CIPHER_block_size(cipher);

    int tempDecryptedLen = 0;
    int finalLen = 0;
    int outDataLen = 0;
    uint8_t *outDataResult = nullptr;
    outDataResult = static_cast<uint8_t *>(malloc(tmpLen + 1));
    if (outDataResult == nullptr) {
        LOGE("LoadWorkKey failed, failed to malloc m_work_key.");
        EVP_CIPHER_CTX_free(ctx);
        return false;
    }
    memset_s(outDataResult, tmpLen + 1, '\0', tmpLen + 1);
    if (EVP_DecryptUpdate(ctx, outDataResult, &tempDecryptedLen, cipherPayLoad, static_cast<int>(cipherPayLoadLen)) != 1) {
        LOGE("EVP_DecryptUpdate failed, %s.", ERR_reason_error_string(ERR_get_error()));
        free(outDataResult);
        EVP_CIPHER_CTX_free(ctx);
        return false;
    }

    int ret = EVP_DecryptFinal_ex(ctx, (outDataResult + tempDecryptedLen), &finalLen);
    if (ret > 0) {
        LOGD("Tag verify successful.");
    } else {
        LOGE("Tag verify failed, %s.", ERR_reason_error_string(ERR_get_error()));
        free(outDataResult);
        EVP_CIPHER_CTX_free(ctx);
        return false;
    }

    tempDecryptedLen += finalLen;
    outDataLen = tempDecryptedLen;
    EVP_CIPHER_CTX_free(ctx);
    string outDataStr((char *)outDataResult, outDataLen);
    outData = outDataStr;
    free(outDataResult);
    return true;
}

bool ValidAesInfo(const AesInfo *aesInfo)
{
    if (aesInfo == nullptr) {
        return false;
    }

    if (aesInfo->keyType != KEY_AES_128 && aesInfo->keyType != KEY_AES_256) {
        return false;
    }

    if (aesInfo->keyType == KEY_AES_128 && aesInfo->strKey.length() != (CAE_AES128_KEY_LEN * 2)) {
        return false;
    }

    if (aesInfo->keyType == KEY_AES_256 && aesInfo->strKey.length() != (CAE_AES256_KEY_LEN * 2)) {
        return false;
    }

    return !(aesInfo->strIv.length() == 0 || aesInfo->tagLen != 16);
}

std::vector<uint8_t> HexToBytes(const std::string &hex)
{
    std::vector<uint8_t> bytes;
    for (uint32_t i = 0; i < hex.length(); i += 2) {
        std::string byteString = hex.substr(i, 2);
        auto byte = static_cast<uint8_t>(strtol(byteString.c_str(), nullptr, 16));
        bytes.push_back(byte);
    }
    return bytes;
}